package com.stox.client.ui.javafx.chartingy.model.chart;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.geometry.Bounds;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;

import com.stox.client.ui.javafx.chartingy.model.axis.ValueAxis;
import com.stox.client.ui.javafx.chartingy.model.plot.Plot;
import com.stox.client.ui.javafx.chartingy.model.plot.PricePlot;
import com.stox.client.ui.javafx.chartingy.model.plot.VolumePlot;
import com.stox.client.ui.javafx.chartingy.model.shape.Shape;
import com.stox.client.ui.javafx.chartingy.util.PlotIndexComparator;
import com.stox.client.ui.javafx.chartingy.view.ChartingView;
import com.stox.client.ui.javafx.chartingy.widget.Grid;
import com.stox.client.ui.javafx.core.StoxPlatform;
import com.stox.client.ui.javafx.core.util.CSS;
import com.stox.client.ui.javafx.core.util.UI;

public class Chart<T, V> extends BorderPane {
	private static Color[] colors = {Color.BLUE, Color.BLUEVIOLET, Color.BROWN, Color.BURLYWOOD, Color.CADETBLUE, Color.CHOCOLATE};

	private final Pane chartContentArea = new Pane() {
		@Override
		protected void layoutChildren() {
			// No Op
		};
	};
	private double max = Double.MIN_VALUE;
	private double min = Double.MAX_VALUE;
	private final ChartingView chartingView;
	private final VBox plotInfoContentPane = new VBox();
	private final Grid grid = new Grid(this);
	private final GlassPane glassPane = new GlassPane(this);
	private final ValueAxis valueAxis = new ValueAxis(this);
	private final List<Shape> shapes = new ArrayList<Shape>();
	private final PlotIndexComparator comparator = new PlotIndexComparator();
	private final ListProperty<Plot<T, V>> plotsProperty = new SimpleListProperty<Plot<T, V>>(FXCollections.observableArrayList());
	private final ObjectProperty<Plot<T, V>> primaryPlotProperty = new SimpleObjectProperty<Plot<T, V>>();

	public Chart(final ChartingView chartingView) {
		this.chartingView = chartingView;

		glassPane.getChildren().add(plotInfoContentPane);
		setCenter(new StackPane(grid, chartContentArea, glassPane));
		setRight(valueAxis);

		UI.style(plotInfoContentPane, CSS.plotInfoContentPane);
		UI.style(chartContentArea, CSS.undecorated);
		UI.style(this, CSS.undecorated, CSS.chart);
		layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
			@Override
			public void changed(final ObservableValue<? extends Bounds> observable, final Bounds oldValue, final Bounds newValue) {
				StoxPlatform.runLater(new Runnable() {
					@Override
					public void run() {
						if ((newValue.getHeight() > 0) && (newValue.getWidth() > 0)) {
							updateAxisBounds();
						}
					}
				});
			}
		});
		primaryPlotProperty.addListener(new ChangeListener<Plot<T, V>>() {
			@Override
			public void changed(final ObservableValue<? extends Plot<T, V>> observable, final Plot<T, V> oldValue, final Plot<T, V> newValue) {
				if (null != oldValue) {
					oldValue.setChart(null);
					chartContentArea.getChildren().remove(oldValue);
					plotInfoContentPane.getChildren().remove(oldValue.getPlotInfoPanel());
				}
				if (null == newValue) {
					chartingView.getCharts().remove(Chart.this);
				} else {
					chartingView.getCharts().remove(newValue.getChart());
					if (plotsProperty.contains(newValue)) {
						plotsProperty.remove(newValue);
					}
					newValue.setChart(Chart.this);
					chartContentArea.getChildren().add(newValue);
					newValue.setColor(Color.BLUE); // TODO should come from preferences
					if (!Chart.this.equals(chartingView.getPrimaryChart())) {
						plotInfoContentPane.getChildren().add(newValue.getPlotInfoPanel());
					}

				}
			}
		});
		plotsProperty.addListener(new ListChangeListener<Plot<T, V>>() {
			@Override
			public void onChanged(final ListChangeListener.Change<? extends Plot<T, V>> change) {
				while (change.next()) {
					if (change.wasAdded()) {
						for (final Plot<T, V> plot : change.getAddedSubList()) {
							if (null != plot.getChart()) {
								plot.getChart().getPlots().remove(plot);
								if (plot.equals(plot.getChart().getPrimaryPlot())) {
									chartingView.getCharts().remove(plot.getChart());
								}
							}
							if (plot instanceof VolumePlot) {

							} else {
								int index = change.getList().indexOf(plot);
								index = index > 0 ? index - 1 : 0;
								plot.setColor(colors[index]);
							}

							plot.setChart(Chart.this);
							chartContentArea.getChildren().add(plot);
							plotInfoContentPane.getChildren().add(plot.getPlotInfoPanel());
						}
					} else if (change.wasRemoved()) {
						for (final Plot<T, V> plot : change.getRemoved()) {
							plot.setChart(null);
							chartContentArea.getChildren().remove(plot);
							plotInfoContentPane.getChildren().remove(plot.getPlotInfoPanel());
						}
					}
				}
				FXCollections.sort(chartContentArea.getChildren(), new PlotIndexComparator());
			}
		});
	}

	public void updateAxisBounds() {
		final Plot<T, V> primaryPlot = primaryPlotProperty.get();
		if (null != primaryPlot) {
			max = Double.MIN_VALUE;
			min = Double.MAX_VALUE;
			primaryPlot.updateAxisBounds();
			for (final Plot<T, V> plot : plotsProperty) {
				plot.updateAxisBounds();
			}
			if (!valueAxis.isZoomable()) {
				if (chartingView.isMaintainRatio()) {
					valueAxis.updateAxisBounds(max, min);
				} else {
					valueAxis.updateAxisBounds(primaryPlot.getMax(), primaryPlot.getMin());
				}
			}

			layoutChartChildren();
		}
	}

	public void layoutChartChildren() {
		if (null != primaryPlotProperty.get()) {
			primaryPlotProperty.get().layoutChartChildren();
			for (final Plot<T, V> plot : plotsProperty) {
				plot.layoutChartChildren();
			}
			for (final Shape shape : shapes) {
				shape.update();
			}
		}
	}

	public void load() {
		if (null != primaryPlotProperty.get()) {
			primaryPlotProperty.get().load();
			for (final Plot<T, V> plot : plotsProperty) {
				plot.load();
			}
		}
	}

	public void addShape(final Shape shape) {
		getChildren().add(shape);
		shapes.add(shape);
	}

	public void removeShape(final Shape shape) {
		getChildren().remove(shape);
		shapes.remove(shape);
	}

	public void removeAllShapes() {
		getChildren().removeAll(shapes);
		shapes.clear();
	}

	public List<Shape> getShapes(){
		return Collections.unmodifiableList(shapes);
	}
	
	public void updateZOrder() {
		Collections.sort(getChildren(), comparator);
	}

	public List<Plot<T, V>> getPlots() {
		return plotsProperty;
	}

	public ListProperty<Plot<T, V>> plotsProperty() {
		return plotsProperty;
	}

	public Plot<T, V> getPrimaryPlot() {
		return primaryPlotProperty.get();
	}

	public void setPrimaryPlot(final Plot<T, V> plot) {
		primaryPlotProperty.set(plot);
	}

	public ObjectProperty<Plot<T, V>> primaryPlotProperty() {
		return primaryPlotProperty;
	}

	public GlassPane getGlassPane() {
		return glassPane;
	}

	public Region getChartContentArea() {
		return chartContentArea;
	}

	public ValueAxis getValueAxis() {
		return valueAxis;
	}

	public ChartingView getChartingView() {
		return chartingView;
	}

	public VBox getPlotInfoContentPane() {
		return plotInfoContentPane;
	}

	public Grid getGrid() {
		return grid;
	}

	public double getMax() {
		return max;
	}

	public void setMax(final double max) {
		this.max = max;
	}

	public double getMin() {
		return min;
	}

	public void setMin(final double min) {
		this.min = min;
	}

	/**
	 * Utility method
	 *
	 * @return
	 */
	public PrimaryChart primaryChart() {
		return chartingView.getPrimaryChart();
	}

	public PricePlot primaryPlot() {
		return primaryChart().getPrimaryPlot();
	}
}
